package com.actionpals.components.core
{
	import com.actionpals.base.BaseSprite;
	import com.actionpals.enum.InvalidationType;
	import com.actionpals.events.ComponentEvent;
	
	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.utils.Dictionary;

	public class UIComponent extends BaseSprite
	{
		public static var inCallLaterPhase:Boolean = false;
		
		protected var _callLaterMethods:Dictionary;
		protected var _invalidateFlag:Boolean = false;
		protected var _enabled:Boolean = true;
		protected var _invalidHash:Object;
		protected var _uiFocusRect:DisplayObject;
		protected var _focusPadding:Number;
		protected var _isFocused:Boolean = false;
		protected var _width:Number;
		protected var _height:Number;
		protected var _x:Number;
		protected var _y:Number;
		protected var _startWidth:Number;
		protected var _startHeight:Number;
		
		public function UIComponent()
		{
			super();
			
			init();
		}
		
		public override function get enabled():Boolean { return _enabled; }
		public override function set enabled( value:Boolean ):void
		{
			if( value == _enabled ) { return; }
			_enabled = value;
			invalidate( InvalidationType.STATE );
		}
		
		public function get uiFocusRect():DisplayObject
		{
			return _uiFocusRect;
		}
		public function set uiFocusRect( value:DisplayObject ):void
		{
			_uiFocusRect = value;
		}
		
		public function get focusPadding():Number
		{
			return _focusPadding;
		}
		public function set focusPadding( value:Number ):void
		{
			_focusPadding = value;
		}
		
		public override function resize( width:Number, height:Number ):void
		{
			_width = width;
			_height = height;
			invalidate( InvalidationType.SIZE );
			dispatchEvent( new ComponentEvent( ComponentEvent.SIZE, false ) );
		}
		
		public override function get width():Number { return _width; }
		public override function set width( value:Number ):void
		{
			if( _width == value ) { return; }
			resize( value, height );
		}
		
		public override function get height():Number { return _height; }
		public override function set height( value:Number ):void
		{
			if( _height == value ) { return; }
			resize( width, value );
		}
		
		public override function move( x:Number, y:Number ):void
		{
			_x = x;
			_y = y;
			super.x = Math.round( x );
			super.y = Math.round( y );
			dispatchEvent( new ComponentEvent( ComponentEvent.MOVE ) );
		}
		
		public override function get x():Number { return ( isNaN( _x ) ) ? super.x : _x; }
		public override function set x( value:Number ):void
		{
			move( value, _y );
		}
		
		public override function get y():Number { return ( isNaN( _y ) ) ? super.y : _y; }
		public override function set y( value:Number ):void
		{
			move( _x, value );	
		}
		
		public override function get scaleX():Number
		{
			return width/_startWidth;
		}
		public override function set scaleX( value:Number ):void
		{
			resize( _startWidth*value, height );
		}
		
		public override function get scaleY():Number
		{
			return height/_startHeight;
		}
		public override function set scaleY( value:Number ):void
		{
			resize( width, _startHeight*value );
		}
		
		public override function scale( x:Number, y:Number ):void
		{
			super.scaleX = x;
			super.scaleY = y;
		}
		
		protected function getScaleY():Number
		{
			return super.scaleY;
		}
		protected function setScaleY( value:Number ):void
		{
			super.scaleY = value;
		}
		
		protected function getScaleX():Number
		{
			return super.scaleX;
		}
		protected function setScaleX( value:Number ):void
		{
			super.scaleX = value;
		}
		
		public override function get visible():Boolean
		{
			return super.visible;	
		}
		public override function set visible( value:Boolean ):void
		{
			if( super.visible == value ) { return; }
			super.visible = value;
			var eventType:String = value ? ComponentEvent.SHOW : ComponentEvent.HIDE;
			dispatchEvent( new ComponentEvent( eventType, true ) );
		}
		
		public function validateNow():void
		{
			invalidate( InvalidationType.ALL, false );
			draw();
		}
		
		public function invalidate( property:String=InvalidationType.ALL, callLater:Boolean=true ):void
		{
			_invalidHash[ property ] = true;
			if( callLater ) { this.callLater( draw ); }
		}
		
		public function drawNow():void
		{
			draw();
		}
		
		public function drawFocus( focused:Boolean ):void
		{
			_isFocused = focused;
			
			if( uiFocusRect != null && contains( uiFocusRect ) )
			{
				removeChild( uiFocusRect );
				uiFocusRect = null;
			}
			
			if( focused )
			{
				if( uiFocusRect == null ) { return; }
				uiFocusRect.x = -focusPadding;
				uiFocusRect.y = -focusPadding;
				uiFocusRect.width = width + ( focusPadding*2 );
				uiFocusRect.height = height + ( focusPadding*2 );
				addChildAt( uiFocusRect, 0 );
			}
		}
		
		public function setFocus():void
		{
			if( stage )
			{
				stage.focus = this;
			}
		}
		
		public function getFocus():InteractiveObject
		{
			if( stage )
			{
				return stage.focus;
			}
			return null;
		}
		
		protected function init():void
		{
			_invalidHash = {};
			_callLaterMethods = new Dictionary();
			
			configUI();
			invalidate( InvalidationType.ALL );
			focusRect = false;
			
			if( tabEnabled )
			{
				addEventListener( FocusEvent.FOCUS_IN, focusInHandler );
				addEventListener( FocusEvent.FOCUS_OUT, focusOutHandler );
				addEventListener( KeyboardEvent.KEY_DOWN, keyDownHandler );
				addEventListener( KeyboardEvent.KEY_UP, keyUpHandler );
			}
		}
		
		protected function configUI():void
		{
			var r:Number = rotation;
			rotation = 0;
			var w:Number = super.width;
			var h:Number = super.height;
			super.scaleX = super.scaleY = 1;
			resize( w, h );
			move( super.x, super.y );
			rotation = r;
			_startWidth = w;
			_startHeight = h;
		}
		
		protected function isInvalid( property:String, ...properties:Array ):Boolean
		{
			if( _invalidHash[ property ] || _invalidHash[ InvalidationType.ALL ]) { return true; }
			while( properties.length > 0 )
			{
				if( _invalidHash[ properties.pop() ] ) { return true; }
			}
			return false
		}
		
		protected function validate():void
		{
			_invalidHash = {};
		}
		
		protected function draw():void
		{
			if( isInvalid( InvalidationType.SIZE, InvalidationType.STYLES ) )
			{
				if( _isFocused ) { drawFocus( true ); }
			}
			validate();
		}
		
		protected function callLater(fn:Function):void
		{
			if( inCallLaterPhase ) { return; }
			
			_callLaterMethods[ fn ] = true;
			if( stage != null )
			{
				stage.addEventListener( Event.RENDER, callLaterDispatcher, false, 0, true );
				stage.invalidate();				
			}
			else
			{
				addEventListener( Event.ADDED_TO_STAGE, callLaterDispatcher, false, 0, true );
			}
		}
		
		private function callLaterDispatcher( evt:Event ):void
		{
			if( evt.type == Event.ADDED_TO_STAGE )
			{
				removeEventListener( Event.ADDED_TO_STAGE, callLaterDispatcher );
				stage.addEventListener( Event.RENDER, callLaterDispatcher, false, 0, true );
				stage.invalidate();
				
				return;
			}
			else
			{
				evt.target.removeEventListener( Event.RENDER, callLaterDispatcher );
				if( stage == null )
				{
					addEventListener( Event.ADDED_TO_STAGE, callLaterDispatcher, false, 0, true );
					return;
				}
			}

			inCallLaterPhase = true;
			
			var methods:Dictionary = _callLaterMethods;
			for( var method:Object in methods )
			{
				method();
				delete( methods[ method ] );
			}
			inCallLaterPhase = false;
		}
		
		protected function isOurFocus( target:DisplayObject ):Boolean
		{
			return( target == this );
		}
		
		protected function focusInHandler( evt:FocusEvent ):void
		{
			if( isOurFocus( evt.target as DisplayObject ) )
			{
				drawFocus( true );
				_isFocused = true;
			}
		}
		
		protected function focusOutHandler( evt:FocusEvent ):void
		{
			if( isOurFocus( evt.target as DisplayObject ) )
			{
				drawFocus( false );
				_isFocused = false;
			}
		}
		
		protected function keyDownHandler( evt:KeyboardEvent ):void
		{
			// You must override this function if your component accepts focus
		}
		
		protected function keyUpHandler( evt:KeyboardEvent ):void
		{
			// You must override this function if your component accepts focus
		}
		
	}
}